/*******************************************************************************
 * Copyright (c) 2000, 2020 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
 *     Jesper S Moller - Contributions for
 *							bug 382701 - [1.8][compiler] Implement semantic analysis of Lambda expressions & Reference expression
 *                          Bug 384687 - [1.8] Wildcard type arguments should be rejected for lambda and reference expressions
 *							Bug 416885 - [1.8][compiler]IncompatibleClassChange error (edit)
 *	   Stephan Herrmann - Contribution for
 *							bug 402028 - [1.8][compiler] null analysis for reference expressions
 *							bug 404649 - [1.8][compiler] detect illegal reference to indirect or redundant super via I.super.m() syntax
 *							Bug 392099 - [1.8][compiler][null] Apply null annotation on types for null analysis
 *							Bug 415850 - [1.8] Ensure RunJDTCoreTests can cope with null annotations enabled
 *							Bug 400874 - [1.8][compiler] Inference infrastructure should evolve to meet JLS8 18.x (Part G of JSR335 spec)
 *							Bug 423504 - [1.8] Implement "18.5.3 Functional Interface Parameterization Inference"
 *							Bug 424637 - [1.8][compiler][null] AIOOB in ReferenceExpression.resolveType with a method reference to Files::walk
 *							Bug 424415 - [1.8][compiler] Eventual resolution of ReferenceExpression is not seen to be happening.
 *							Bug 424403 - [1.8][compiler] Generic method call with method reference argument fails to resolve properly.
 *							Bug 427196 - [1.8][compiler] Compiler error for method reference to overloaded method
 *							Bug 427438 - [1.8][compiler] NPE at org.aspectj.org.eclipse.jdt.internal.compiler.ast.ConditionalExpression.generateCode(ConditionalExpression.java:280)
 *							Bug 428264 - [1.8] method reference of generic class causes problems (wrong inference result or NPE)
 *							Bug 392238 - [1.8][compiler][null] Detect semantically invalid null type annotations
 *							Bug 426537 - [1.8][inference] Eclipse compiler thinks I<? super J> is compatible with I<J<?>> - raw type J involved
 *							Bug 435570 - [1.8][null] @NonNullByDefault illegally tries to affect "throws E"
 *							Bug 435689 - [1.8][inference] Type inference not occurring with lambda expression and method reference
 *							Bug 438383 - [1.8][null] Bogus warning: Null type safety at method return type
 *							Bug 434483 - [1.8][compiler][inference] Type inference not picked up with method reference
 *							Bug 441734 - [1.8][inference] Generic method with nested parameterized type argument fails on method reference
 *							Bug 438945 - [1.8] NullPointerException InferenceContext18.checkExpression in java 8 with generics, primitives, and overloading
 *							Bug 452788 - [1.8][compiler] Type not correctly inferred in lambda expression
 *							Bug 448709 - [1.8][null] ensure we don't infer types that violate null constraints on a type parameter's bound
 *							Bug 459967 - [null] compiler should know about nullness of special methods like MyEnum.valueOf()
 *							Bug 466713 - Null Annotations: NullPointerException using <int @Nullable []> as Type Param
 *							Bug 470542 - NullPointerException in ReferenceExpression.isPotentiallyCompatibleWith (962)
 *        Andy Clement (GoPivotal, Inc) aclement@gopivotal.com - Contribution for
 *                          Bug 383624 - [1.8][compiler] Revive code generation support for type annotations (from Olivier's work)
 *******************************************************************************/

package org.aspectj.org.eclipse.jdt.internal.compiler.ast;

import static org.aspectj.org.eclipse.jdt.internal.compiler.ast.ExpressionContext.INVOCATION_CONTEXT;

import java.util.HashMap;
import org.aspectj.org.eclipse.jdt.core.compiler.CharOperation;
import org.aspectj.org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.aspectj.org.eclipse.jdt.internal.compiler.CompilationResult;
import org.aspectj.org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.TypeReference.AnnotationPosition;
import org.aspectj.org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.aspectj.org.eclipse.jdt.internal.compiler.codegen.CodeStream;
import org.aspectj.org.eclipse.jdt.internal.compiler.codegen.ConstantPool;
import org.aspectj.org.eclipse.jdt.internal.compiler.codegen.Opcodes;
import org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.aspectj.org.eclipse.jdt.internal.compiler.flow.FieldInitsFakingFlowContext;
import org.aspectj.org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.aspectj.org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.aspectj.org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo;
import org.aspectj.org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.aspectj.org.eclipse.jdt.internal.compiler.impl.Constant;
import org.aspectj.org.eclipse.jdt.internal.compiler.impl.IrritantSet;
import org.aspectj.org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ImplicitNullAnnotationVerifier;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.InferenceContext18;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.InvocationSite;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ParameterizedGenericMethodBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ParameterizedMethodBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.PolyTypeBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ProblemReasons;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.parser.Parser;
import org.aspectj.org.eclipse.jdt.internal.compiler.parser.Scanner;

public class ReferenceExpression extends FunctionalExpression implements IPolyExpression, InvocationSite {
	// secret variable name
	private static final String SecretReceiverVariableName = " rec_"; //$NON-NLS-1$
	private static final char[] ImplicitArgName = " arg".toCharArray(); //$NON-NLS-1$
	// secret variable for codegen
	public LocalVariableBinding receiverVariable;
	public Expression lhs;
	public TypeReference [] typeArguments;
	public char [] selector;

	public int nameSourceStart;

	public TypeBinding receiverType;
	public boolean haveReceiver;
	public TypeBinding[] resolvedTypeArguments;
	private boolean typeArgumentsHaveErrors;

	MethodBinding syntheticAccessor;	// synthetic accessor for inner-emulation
	private int depth;
	private MethodBinding exactMethodBinding; // != null ==> exact method reference.
	private boolean receiverPrecedesParameters = false;
	private TypeBinding[] freeParameters; // descriptor parameters as used for method lookup - may or may not include the receiver
	private boolean checkingPotentialCompatibility;
	private MethodBinding[] potentialMethods = Binding.NO_METHODS;
	protected ReferenceExpression original;
	private HashMap<TypeBinding, ReferenceExpression> copiesPerTargetType;
	public char[] text; // source representation of the expression.
	private HashMap<ParameterizedGenericMethodBinding, InferenceContext18> inferenceContexts;

	// the scanner used when creating this expression, may be a RecoveryScanner (with proper RecoveryScannerData),
	// need to keep it so copy() can parse in the same mode (normal/recovery):
	private Scanner scanner;

	public ReferenceExpression(Scanner scanner) {
		super();
		this.original = this;
		this.scanner = scanner;
	}

	public void initialize(CompilationResult result, Expression expression, TypeReference [] optionalTypeArguments, char [] identifierOrNew, int sourceEndPosition) {
		super.setCompilationResult(result);
		this.lhs = expression;
		this.typeArguments = optionalTypeArguments;
		this.selector = identifierOrNew;
		this.sourceStart = expression.sourceStart;
		this.sourceEnd = sourceEndPosition;
	}

	private ReferenceExpression copy() {
		final Parser parser = new Parser(this.enclosingScope.problemReporter(), false);
		final ICompilationUnit compilationUnit = this.compilationResult.getCompilationUnit();
		final char[] source = compilationUnit != null ? compilationUnit.getContents() : this.text;
		parser.scanner = this.scanner;
		ReferenceExpression copy =  (ReferenceExpression) parser.parseExpression(source, compilationUnit != null ? this.sourceStart : 0, this.sourceEnd - this.sourceStart + 1,
										this.enclosingScope.referenceCompilationUnit(), false /* record line separators */);
		copy.original = this;
		copy.sourceStart = this.sourceStart;
		copy.sourceEnd = this.sourceEnd;
		return copy;
	}

	private boolean shouldGenerateSecretReceiverVariable() {
		if (isMethodReference() && this.haveReceiver) {
			if (this.lhs instanceof Invocation)
				return true;
			else {
				return new ASTVisitor() {
					boolean accessesnonFinalOuterLocals;

					@Override
					public boolean visit(SingleNameReference name, BlockScope skope) {
						Binding local = skope.getBinding(name.getName(), ReferenceExpression.this);
						if (local instanceof LocalVariableBinding) {
							LocalVariableBinding localBinding = (LocalVariableBinding) local;
							if (!localBinding.isFinal() && !localBinding.isEffectivelyFinal()) {
								this.accessesnonFinalOuterLocals = true;
							}
						}
						return false;
					}

					public boolean accessesnonFinalOuterLocals() {
						ReferenceExpression.this.lhs.traverse(this, ReferenceExpression.this.enclosingScope);
						return this.accessesnonFinalOuterLocals;
					}
				}.accessesnonFinalOuterLocals();
			}
		}
		return false;
	}
	public void generateImplicitLambda(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {

		ReferenceExpression copy = copy();

		int argc = this.descriptor.parameters.length;

		LambdaExpression implicitLambda = new LambdaExpression(this.compilationResult, false, (this.binding.modifiers & ExtraCompilerModifiers.AccGenericSignature) != 0);
		Argument [] arguments = new Argument[argc];
		for (int i = 0; i < argc; i++)
			arguments[i] = new Argument(CharOperation.append(ImplicitArgName, Integer.toString(i).toCharArray()), 0, null, 0, true);
		implicitLambda.setArguments(arguments);
		implicitLambda.setExpressionContext(this.expressionContext);
		implicitLambda.setExpectedType(this.expectedType);

		int parameterShift = this.receiverPrecedesParameters ? 1 : 0;
		Expression [] argv = new SingleNameReference[argc - parameterShift];
		for (int i = 0, length = argv.length; i < length; i++) {
			char[] name = CharOperation.append(ImplicitArgName, Integer.toString((i + parameterShift)).toCharArray());
			argv[i] = new SingleNameReference(name, 0);
		}
		boolean generateSecretReceiverVariable = shouldGenerateSecretReceiverVariable();
		if (isMethodReference()) {
			if (generateSecretReceiverVariable) {
				this.lhs.generateCode(currentScope, codeStream, true);
				codeStream.store(this.receiverVariable, false);
				codeStream.addVariable(this.receiverVariable);
			}
			MessageSend message = new MessageSend();
			message.selector = this.selector;
			Expression receiver = generateSecretReceiverVariable ? new SingleNameReference(this.receiverVariable.name, 0) : copy.lhs;
			message.receiver = this.receiverPrecedesParameters ?
					new SingleNameReference(CharOperation.append(ImplicitArgName, Integer.toString(0).toCharArray()), 0) : receiver;
			message.typeArguments = copy.typeArguments;
			message.arguments = argv;
			implicitLambda.setBody(message);
		} else if (isArrayConstructorReference()) {
			// We don't care for annotations, source positions etc. They are immaterial, just drop.
			ArrayAllocationExpression arrayAllocationExpression = new ArrayAllocationExpression();
			arrayAllocationExpression.dimensions = new Expression[] { argv[0] };
			if (this.lhs instanceof ArrayTypeReference) {
				ArrayTypeReference arrayTypeReference = (ArrayTypeReference) this.lhs;
				arrayAllocationExpression.type = arrayTypeReference.dimensions == 1 ? new SingleTypeReference(arrayTypeReference.token, 0L) :
																new ArrayTypeReference(arrayTypeReference.token, arrayTypeReference.dimensions - 1, 0L);
			} else {
				ArrayQualifiedTypeReference arrayQualifiedTypeReference = (ArrayQualifiedTypeReference) this.lhs;
				arrayAllocationExpression.type = arrayQualifiedTypeReference.dimensions == 1 ? new QualifiedTypeReference(arrayQualifiedTypeReference.tokens, arrayQualifiedTypeReference.sourcePositions)
																: new ArrayQualifiedTypeReference(arrayQualifiedTypeReference.tokens, arrayQualifiedTypeReference.dimensions - 1,
																		arrayQualifiedTypeReference.sourcePositions);
			}
			implicitLambda.setBody(arrayAllocationExpression);
		} else {
			AllocationExpression allocation = new AllocationExpression();
			if (this.lhs instanceof TypeReference) {
				allocation.type = (TypeReference) this.lhs;
			} else if (this.lhs instanceof SingleNameReference) {
				allocation.type = new SingleTypeReference(((SingleNameReference) this.lhs).token, 0);
			} else if (this.lhs instanceof QualifiedNameReference) {
				allocation.type = new QualifiedTypeReference(((QualifiedNameReference) this.lhs).tokens, new long [((QualifiedNameReference) this.lhs).tokens.length]);
			} else {
				throw new IllegalStateException("Unexpected node type"); //$NON-NLS-1$
			}
			allocation.typeArguments = copy.typeArguments;
			allocation.arguments = argv;
			implicitLambda.setBody(allocation);
		}

		// Process the lambda, taking care not to double report diagnostics. Don't expect any from resolve, Any from code generation should surface, but not those from flow analysis.
		BlockScope lambdaScope = this.receiverVariable != null ? this.receiverVariable.declaringScope : currentScope;
		IErrorHandlingPolicy oldPolicy = lambdaScope.problemReporter().switchErrorHandlingPolicy(silentErrorHandlingPolicy);
		try {
			implicitLambda.resolveType(lambdaScope, true);
			implicitLambda.analyseCode(lambdaScope,
					new FieldInitsFakingFlowContext(null, this, Binding.NO_EXCEPTIONS, null, lambdaScope, FlowInfo.DEAD_END),
					UnconditionalFlowInfo.fakeInitializedFlowInfo(lambdaScope.outerMostMethodScope().analysisIndex, lambdaScope.referenceType().maxFieldCount));
		} finally {
			lambdaScope.problemReporter().switchErrorHandlingPolicy(oldPolicy);
		}
		SyntheticArgumentBinding[] outerLocals = this.receiverType.syntheticOuterLocalVariables();
		for (int i = 0, length = outerLocals == null ? 0 : outerLocals.length; i < length; i++)
			implicitLambda.addSyntheticArgument(outerLocals[i].actualOuterLocalVariable);

		implicitLambda.generateCode(lambdaScope, codeStream, valueRequired);
		if (generateSecretReceiverVariable) {
			codeStream.removeVariable(this.receiverVariable);
		}
	}

	private boolean shouldGenerateImplicitLambda(BlockScope currentScope) {
		// these cases are either too complicated, impossible to handle or result in significant code duplication
		return (this.binding.isVarargs() ||
				(isConstructorReference() && this.receiverType.syntheticOuterLocalVariables() != null && this.shouldCaptureInstance) ||
				this.requiresBridges() || // bridges.
				!isDirectCodeGenPossible());
		// To fix: We should opt for direct code generation wherever possible.
	}
	private boolean isDirectCodeGenPossible() {
		if (this.binding != null) {
			if (isMethodReference() && this.syntheticAccessor == null) {
				if (TypeBinding.notEquals(this.binding.declaringClass, this.lhs.resolvedType.erasure())) {
					// reference to a method declared by an inaccessible type accessed via a
					// subtype - normally a bridge method would be present to facilitate
					// this access, unless the method is final/static/default, in which case, direct access to
					// the method is not possible, an implicit lambda is needed
					if (!this.binding.declaringClass.canBeSeenBy(this.enclosingScope)) {
						if (this.binding.isDefaultMethod())
							return false; // workaround for bug in MethodHandle lookup, see https://bugs.openjdk.java.net/browse/JDK-8068253
						return !(this.binding.isFinal() || this.binding.isStatic());
					}
				}
			}
			TypeBinding[] descriptorParams = this.descriptor.parameters;
			TypeBinding[] origParams = this.binding.original().parameters;
			TypeBinding[] origDescParams = this.descriptor.original().parameters;
			int offset = this.receiverPrecedesParameters ? 1 : 0;
			for (int i = 0; i < descriptorParams.length - offset; i++) {
				TypeBinding descType = descriptorParams[i + offset];
				TypeBinding origDescType = origDescParams[i + offset];
				if (descType.isIntersectionType18() ||
						(descType.isTypeVariable() && ((TypeVariableBinding) descType).boundsCount() > 1)) {
					return CharOperation.equals(origDescType.signature(), origParams[i].signature());
				}
			}
		}
		return true;
	}
	@Override
	public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {
		this.actualMethodBinding = this.binding; // grab before synthetics come into play.
		// Handle some special cases up front and transform them into implicit lambdas.
		if (shouldGenerateImplicitLambda(currentScope)) {
			generateImplicitLambda(currentScope, codeStream, valueRequired);
			return;
		}
		SourceTypeBinding sourceType = currentScope.enclosingSourceType();
		if (this.receiverType.isArrayType()) {
			char [] lambdaName = CharOperation.concat(TypeConstants.ANONYMOUS_METHOD, Integer.toString(this.ordinal).toCharArray());
			if (isConstructorReference()) {
				this.actualMethodBinding = this.binding = sourceType.addSyntheticArrayMethod((ArrayBinding) this.receiverType, SyntheticMethodBinding.ArrayConstructor, lambdaName);
			} else if (CharOperation.equals(this.selector, TypeConstants.CLONE)) {
				this.actualMethodBinding = this.binding = sourceType.addSyntheticArrayMethod((ArrayBinding) this.receiverType, SyntheticMethodBinding.ArrayClone, lambdaName);
			}
		} else if (this.syntheticAccessor != null) {
			if (this.lhs.isSuper() || isMethodReference())
				this.binding = this.syntheticAccessor;
		} else { // cf. MessageSend.generateCode()'s call to CodeStream.getConstantPoolDeclaringClass. We have extracted the relevant portions sans side effect here.
			if (this.binding != null && isMethodReference()) {
				if (TypeBinding.notEquals(this.binding.declaringClass, this.lhs.resolvedType.erasure())) {
					if (!this.binding.declaringClass.canBeSeenBy(currentScope)) {
						this.binding = new MethodBinding(this.binding.original(), (ReferenceBinding) this.lhs.resolvedType.erasure());
					}
				}
			}
		}
		int pc = codeStream.position;
		StringBuffer buffer = new StringBuffer();
		int argumentsSize = 0;
		buffer.append('(');
		if (this.haveReceiver) {
			this.lhs.generateCode(currentScope, codeStream, true);
			if (isMethodReference() && !this.lhs.isThis() && !this.lhs.isSuper()) {
				MethodBinding mb = currentScope.getJavaLangObject().getExactMethod(TypeConstants.GETCLASS,
						Binding.NO_PARAMETERS, currentScope.compilationUnitScope());
				codeStream.dup();
				codeStream.invoke(Opcodes.OPC_invokevirtual, mb, mb.declaringClass);
				codeStream.pop();
			}
			if (this.lhs.isSuper() && !this.actualMethodBinding.isPrivate()) {
				if (this.lhs instanceof QualifiedSuperReference) {
					QualifiedSuperReference qualifiedSuperReference = (QualifiedSuperReference) this.lhs;
					TypeReference qualification = qualifiedSuperReference.qualification;
					if (qualification.resolvedType.isInterface()) {
						buffer.append(sourceType.signature());
					} else {
						buffer.append(((QualifiedSuperReference) this.lhs).currentCompatibleType.signature());
					}
				} else {
					buffer.append(sourceType.signature());
				}
			} else {
				buffer.append(this.receiverType.signature());
			}
			argumentsSize = 1;
		} else {
			if (this.isConstructorReference()) {
				ReferenceBinding[] enclosingInstances = Binding.UNINITIALIZED_REFERENCE_TYPES;
				if (this.receiverType.isNestedType()) {
					ReferenceBinding nestedType = (ReferenceBinding) this.receiverType;
					if ((enclosingInstances = nestedType.syntheticEnclosingInstanceTypes()) != null) {
						int length = enclosingInstances.length;
						argumentsSize = length;
						for (int i = 0 ; i < length; i++) {
							ReferenceBinding syntheticArgumentType = enclosingInstances[i];
							buffer.append(syntheticArgumentType.signature());
							Object[] emulationPath = currentScope.getEmulationPath(
									syntheticArgumentType,
									false /* allow compatible match */,
									true /* disallow instance reference in explicit constructor call */);
							codeStream.generateOuterAccess(emulationPath, this, syntheticArgumentType, currentScope);
						}
					} else {
						enclosingInstances = Binding.NO_REFERENCE_TYPES;
					}
				}
				if (this.syntheticAccessor != null) {
					char [] lambdaName = CharOperation.concat(TypeConstants.ANONYMOUS_METHOD, Integer.toString(this.ordinal).toCharArray());
					this.binding = sourceType.addSyntheticFactoryMethod(this.binding, this.syntheticAccessor, enclosingInstances, lambdaName);
					this.syntheticAccessor = null; // add only once
				}
			}
		}
		buffer.append(')');
		buffer.append('L');
		if (this.resolvedType.isIntersectionType18()) {
			buffer.append(this.descriptor.declaringClass.constantPoolName());
		} else {
			buffer.append(this.resolvedType.constantPoolName());
		}
		buffer.append(';');
		if (this.isSerializable) {
			sourceType.addSyntheticMethod(this);
		}
		int invokeDynamicNumber = codeStream.classFile.recordBootstrapMethod(this);
		codeStream.invokeDynamic(invokeDynamicNumber, argumentsSize, 1, this.descriptor.selector, buffer.toString().toCharArray(),
				this.isConstructorReference(), (this.lhs instanceof TypeReference? (TypeReference) this.lhs : null), this.typeArguments,
				this.resolvedType.id, this.resolvedType);
		if (!valueRequired)
			codeStream.pop();
		codeStream.recordPositionsFrom(pc, this.sourceStart);
	}

	@Override
	public void cleanUp() {
		// no more rescanning needed beyond this point, so free the memory:
		if (this.copiesPerTargetType != null) {
			for (ReferenceExpression copy : this.copiesPerTargetType.values())
				copy.scanner = null;
		}
		if (this.original != null && this.original != this) {
			this.original.cleanUp();
		}
		this.scanner = null;
		this.receiverVariable = null;
	}

	public void manageSyntheticAccessIfNecessary(BlockScope currentScope, FlowInfo flowInfo) {

		if ((flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0 || this.binding == null || !this.binding.isValidBinding())
			return;

		MethodBinding codegenBinding = this.binding.original();
		if (codegenBinding.isVarargs())
			return; // completely managed by transforming into implicit lambda expression.

		SourceTypeBinding enclosingSourceType = currentScope.enclosingSourceType();

		if (this.isConstructorReference()) {
			ReferenceBinding allocatedType = codegenBinding.declaringClass;
			if (codegenBinding.isPrivate() &&
					TypeBinding.notEquals(enclosingSourceType, (allocatedType = codegenBinding.declaringClass))) {
				if ((allocatedType.tagBits & TagBits.IsLocalType) != 0) {
					codegenBinding.tagBits |= TagBits.ClearPrivateModifier;
				} else {
					if (currentScope.enclosingSourceType().isNestmateOf(this.binding.declaringClass)) {
						this.syntheticAccessor = codegenBinding;
						return;
					}
					this.syntheticAccessor = ((SourceTypeBinding) allocatedType).addSyntheticMethod(codegenBinding, false);
					currentScope.problemReporter().needToEmulateMethodAccess(codegenBinding, this);
				}
			}
			return;
		}

		// -----------------------------------   Only method references from now on -----------
		if (this.binding.isPrivate()) {
			if (TypeBinding.notEquals(enclosingSourceType, codegenBinding.declaringClass)){
				this.syntheticAccessor = ((SourceTypeBinding)codegenBinding.declaringClass).addSyntheticMethod(codegenBinding, false /* not super access */);
				currentScope.problemReporter().needToEmulateMethodAccess(codegenBinding, this);
			}
			return;
		}

		if (this.lhs.isSuper()) {
			SourceTypeBinding destinationType = enclosingSourceType;
			if (this.lhs instanceof QualifiedSuperReference) { 	// qualified super
				QualifiedSuperReference qualifiedSuperReference = (QualifiedSuperReference) this.lhs;
				TypeReference qualification = qualifiedSuperReference.qualification;
				if (!qualification.resolvedType.isInterface()) // we can't drop the bridge in I, it may not even be a source type.
					destinationType = (SourceTypeBinding) (qualifiedSuperReference.currentCompatibleType);
			}

			this.syntheticAccessor = destinationType.addSyntheticMethod(codegenBinding, true);
			currentScope.problemReporter().needToEmulateMethodAccess(codegenBinding, this);
			return;
		}

		if (this.binding.isProtected() && (this.bits & ASTNode.DepthMASK) != 0 && codegenBinding.declaringClass.getPackage() != enclosingSourceType.getPackage()) {
			SourceTypeBinding currentCompatibleType = (SourceTypeBinding) enclosingSourceType.enclosingTypeAt((this.bits & ASTNode.DepthMASK) >> ASTNode.DepthSHIFT);
			this.syntheticAccessor = currentCompatibleType.addSyntheticMethod(codegenBinding, isSuperAccess());
			currentScope.problemReporter().needToEmulateMethodAccess(codegenBinding, this);
			return;
		}
	}

	@Override
	public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
		// static methods with receiver value never get here
		if (this.haveReceiver) {
			this.lhs.analyseCode(currentScope, flowContext, flowInfo, true);
			this.lhs.checkNPE(currentScope, flowContext, flowInfo);
		} else if (isConstructorReference()) {
			TypeBinding type = this.receiverType.leafComponentType();
			if (type.isNestedType() &&
				type instanceof ReferenceBinding && !((ReferenceBinding)type).isStatic()) {
				currentScope.tagAsAccessingEnclosingInstanceStateOf((ReferenceBinding)type, false);
				this.shouldCaptureInstance = true;
				ReferenceBinding allocatedTypeErasure = (ReferenceBinding) type.erasure();
				if (allocatedTypeErasure.isLocalType()) {
					((LocalTypeBinding) allocatedTypeErasure).addInnerEmulationDependent(currentScope, false);
					// request cascade of accesses
				}
			}
		}
		if (currentScope.compilerOptions().isAnyEnabled(IrritantSet.UNLIKELY_ARGUMENT_TYPE) && this.binding.isValidBinding()
				&& this.binding != null && this.binding.parameters != null) {
			if (this.binding.parameters.length == 1
					&& this.descriptor.parameters.length == (this.receiverPrecedesParameters ? 2 : 1)
					&& !this.binding.isStatic()) {
				final TypeBinding argumentType = this.descriptor.parameters[this.receiverPrecedesParameters ? 1 : 0];
				final TypeBinding actualReceiverType = this.receiverPrecedesParameters ? this.descriptor.parameters[0] : this.binding.declaringClass;
				UnlikelyArgumentCheck argumentCheck = UnlikelyArgumentCheck
						.determineCheckForNonStaticSingleArgumentMethod(argumentType, currentScope, this.selector,
								actualReceiverType, this.binding.parameters);
				if (argumentCheck != null && argumentCheck.isDangerous(currentScope)) {
					currentScope.problemReporter().unlikelyArgumentType(this, this.binding, argumentType,
							argumentCheck.typeToReport, argumentCheck.dangerousMethod);
				}
			} else if (this.binding.parameters.length == 2 && this.descriptor.parameters.length == 2 && this.binding.isStatic()) {
				final TypeBinding argumentType1 = this.descriptor.parameters[0];
				final TypeBinding argumentType2 = this.descriptor.parameters[1];
				UnlikelyArgumentCheck argumentCheck = UnlikelyArgumentCheck
						.determineCheckForStaticTwoArgumentMethod(argumentType2, currentScope, this.selector,
								argumentType1, this.binding.parameters, this.receiverType);
				if (argumentCheck != null && argumentCheck.isDangerous(currentScope)) {
					currentScope.problemReporter().unlikelyArgumentType(this, this.binding, argumentType2,
							argumentCheck.typeToReport, argumentCheck.dangerousMethod);
				}
			}
		}

		if (currentScope.compilerOptions().analyseResourceLeaks) {
			if (this.haveReceiver && CharOperation.equals(this.selector, TypeConstants.CLOSE)) {
				FakedTrackingVariable trackingVariable = FakedTrackingVariable.getCloseTrackingVariable(this.lhs, flowInfo, flowContext);
				if (trackingVariable != null) { // null happens if target is not a local variable or not an AutoCloseable
					trackingVariable.markClosedInNestedMethod(); // there is a close()-call, but we don't know if it will be invoked
				}
			}
		}
		manageSyntheticAccessIfNecessary(currentScope, flowInfo);
		return flowInfo;
	}

	@Override
	public boolean checkingPotentialCompatibility() {
		return this.checkingPotentialCompatibility;
	}

	@Override
	public void acceptPotentiallyCompatibleMethods(MethodBinding[] methods) {
		if (this.checkingPotentialCompatibility)
			this.potentialMethods = methods;
	}

	@Override
	public TypeBinding resolveType(BlockScope scope) {

		final CompilerOptions compilerOptions = scope.compilerOptions();
		TypeBinding lhsType;
    	if (this.constant != Constant.NotAConstant) {
    		this.constant = Constant.NotAConstant;
    		this.enclosingScope = scope;
    		if (this.original == this)
    			this.ordinal = recordFunctionalType(scope);

    		this.lhs.bits |= ASTNode.IgnoreRawTypeCheck;
    		lhsType = this.lhs.resolveType(scope);
    		this.lhs.computeConversion(scope, lhsType, lhsType);
    		if (this.typeArguments != null) {
    			int length = this.typeArguments.length;
    			this.typeArgumentsHaveErrors = compilerOptions.sourceLevel < ClassFileConstants.JDK1_5;
    			this.resolvedTypeArguments = new TypeBinding[length];
    			for (int i = 0; i < length; i++) {
    				TypeReference typeReference = this.typeArguments[i];
    				if ((this.resolvedTypeArguments[i] = typeReference.resolveType(scope, true /* check bounds*/)) == null) {
    					this.typeArgumentsHaveErrors = true;
    				}
    				if (this.typeArgumentsHaveErrors && typeReference instanceof Wildcard) { // resolveType on wildcard always return null above, resolveTypeArgument is the real workhorse.
    					scope.problemReporter().illegalUsageOfWildcard(typeReference);
    				}
    			}
    			if (this.typeArgumentsHaveErrors || lhsType == null)
    				return this.resolvedType = null;
    			if (isConstructorReference() && lhsType.isRawType()) {
    				scope.problemReporter().rawConstructorReferenceNotWithExplicitTypeArguments(this.typeArguments);
    				return this.resolvedType = null;
    			}
    		}
    		if (this.typeArgumentsHaveErrors || lhsType == null)
				return this.resolvedType = null;

    		if (lhsType.problemId() == ProblemReasons.AttemptToBypassDirectSuper)
    			lhsType = lhsType.closestMatch();	// improve resolving experience
        	if (lhsType == null || !lhsType.isValidBinding())
    			return this.resolvedType = null;	// nope, no useful type found

    		this.receiverType = lhsType;
			this.haveReceiver = true;
			if (this.lhs instanceof NameReference) {
				if ((this.lhs.bits & ASTNode.RestrictiveFlagMASK) == Binding.TYPE) {
					this.haveReceiver = false;
				} else if (isConstructorReference()) {
					scope.problemReporter().invalidType(
							this.lhs,
							new ProblemReferenceBinding(((NameReference) this.lhs).getName(), null,
									ProblemReasons.NotFound));
					return this.resolvedType = null;
				}
			} else if (this.lhs instanceof TypeReference) {
				this.haveReceiver = false;
			}
			if (!this.haveReceiver && !this.lhs.isSuper() && !this.isArrayConstructorReference())
				this.receiverType = lhsType.capture(scope, this.sourceStart, this.sourceEnd);

			if (!lhsType.isRawType()) // RawType::m and RawType::new are not exact method references
	    		this.binding = this.exactMethodBinding = isMethodReference() ? scope.getExactMethod(lhsType, this.selector, this) : scope.getExactConstructor(lhsType, this);

    		if (isConstructorReference() && !lhsType.canBeInstantiated()) {
    			scope.problemReporter().cannotInstantiate(this.lhs, lhsType);
    			return this.resolvedType = null;
    		}

    		if (this.lhs instanceof TypeReference && ((TypeReference)this.lhs).hasNullTypeAnnotation(AnnotationPosition.ANY)) {
    			scope.problemReporter().nullAnnotationUnsupportedLocation((TypeReference) this.lhs);
    		}

    		if (isConstructorReference() && lhsType.isArrayType()) {
	        	final TypeBinding leafComponentType = lhsType.leafComponentType();
				if (!leafComponentType.isReifiable()) {
	        		scope.problemReporter().illegalGenericArray(leafComponentType, this);
	        		return this.resolvedType = null;
	        	}
				if (this.typeArguments != null) {
	                scope.problemReporter().invalidTypeArguments(this.typeArguments);
	                return this.resolvedType = null;
	            }
	        	this.binding = this.exactMethodBinding = scope.getExactConstructor(lhsType, this);
	        }
			if (isMethodReference() && this.haveReceiver && (this.original == this)) {
				this.receiverVariable = new LocalVariableBinding(
						(SecretReceiverVariableName + this.nameSourceStart).toCharArray(), this.lhs.resolvedType,
						ClassFileConstants.AccDefault, false);
				scope.addLocalVariable(this.receiverVariable);
				this.receiverVariable.setConstant(Constant.NotAConstant); // not inlinable
				this.receiverVariable.useFlag = LocalVariableBinding.USED;
			}

	    	if (this.expectedType == null && this.expressionContext == INVOCATION_CONTEXT) {
	    		if (compilerOptions.isAnnotationBasedNullAnalysisEnabled && this.binding != null) {
	    			ImplicitNullAnnotationVerifier.ensureNullnessIsKnown(this.binding, scope);
	    		}
	    		return new PolyTypeBinding(this);
			}

    	} else {
    		lhsType = this.lhs.resolvedType;
    		if (this.typeArgumentsHaveErrors || lhsType == null)
				return this.resolvedType = null;
    	}

    	super.resolveType(scope);

		/* For Reference expressions unlike other call sites, we always have a receiver _type_ since LHS of :: cannot be empty.
		   LHS's resolved type == actual receiver type. All code below only when a valid descriptor is available.
		*/
    	if (this.descriptor == null || !this.descriptor.isValidBinding())
    		return this.resolvedType =  null;

    	// Convert parameters into argument expressions for look up.
		TypeBinding[] descriptorParameters = descriptorParametersAsArgumentExpressions();

		if (lhsType.isBaseType()) {
			scope.problemReporter().errorNoMethodFor(this.lhs, lhsType, this.selector, descriptorParameters);
			return this.resolvedType = null;
		}

		/* 15.13: "If a method reference expression has the form super :: [TypeArguments] Identifier or TypeName . super :: [TypeArguments] Identifier,
		   it is a compile-time error if the expression occurs in a static context. ": This is nop since the primary when it resolves
		   itself will complain automatically.

		   15.13: "The immediately enclosing instance of an inner class instance (15.9.2) must be provided for a constructor reference by a lexically
		   enclosing instance of this (8.1.3)", we will actually implement this check in code generation. Emulation path computation will fail if there
		   is no suitable enclosing instance. While this could be pulled up to here, leaving it to code generation is more consistent with Java 5,6,7
		   modus operandi.
		*/

		// handle the special case of array construction first.
		final int parametersLength = descriptorParameters.length;
        if (isConstructorReference() && lhsType.isArrayType()) {
        	if (parametersLength != 1 || scope.parameterCompatibilityLevel(descriptorParameters[0], TypeBinding.INT) == Scope.NOT_COMPATIBLE) {
        		scope.problemReporter().invalidArrayConstructorReference(this, lhsType, descriptorParameters);
        		return this.resolvedType = null;
        	}
        	if (this.descriptor.returnType.isProperType(true) && !lhsType.isCompatibleWith(this.descriptor.returnType) && this.descriptor.returnType.id != TypeIds.T_void) {
        		scope.problemReporter().constructedArrayIncompatible(this, lhsType, this.descriptor.returnType);
        		return this.resolvedType = null;
        	}
            checkNullAnnotations(scope);
        	return this.resolvedType;
        }

        // 15.13.1
        final boolean isMethodReference = isMethodReference();
        this.depth = 0;
        this.freeParameters = descriptorParameters;
        MethodBinding someMethod = null;
        if (isMethodReference) {
        	someMethod = scope.getMethod(this.receiverType, this.selector, descriptorParameters, this);
        } else {
        	if (argumentsTypeElided() && this.receiverType.isRawType()) {
        		boolean[] inferredReturnType = new boolean[1];
	        	someMethod = AllocationExpression.inferDiamondConstructor(scope, this, this.receiverType, this.descriptor.parameters, inferredReturnType);
        	}
        	if (someMethod == null)
        		someMethod = scope.getConstructor((ReferenceBinding) this.receiverType, descriptorParameters, this);
        }
        int someMethodDepth = this.depth, anotherMethodDepth = 0;
    	if (someMethod != null && someMethod.isValidBinding()) {
    		if (someMethod.isStatic() && (this.haveReceiver || this.receiverType.isParameterizedTypeWithActualArguments())) {
    			scope.problemReporter().methodMustBeAccessedStatically(this, someMethod);
    			return this.resolvedType = null;
    		}
        }

    	if (this.lhs.isSuper() && this.lhs.resolvedType.isInterface()) {
    		scope.checkAppropriateMethodAgainstSupers(this.selector, someMethod, this.descriptor.parameters, this);
    	}

        MethodBinding anotherMethod = null;
        this.receiverPrecedesParameters = false;
        if (!this.haveReceiver && isMethodReference && parametersLength > 0) {
        	final TypeBinding potentialReceiver = descriptorParameters[0];
        	if (potentialReceiver.isCompatibleWith(this.receiverType, scope)) {
        		TypeBinding typeToSearch = this.receiverType;
        		if (this.receiverType.isRawType()) {
        			TypeBinding superType = potentialReceiver.findSuperTypeOriginatingFrom(this.receiverType);
        			if (superType != null)
        				typeToSearch = superType.capture(scope, this.sourceStart, this.sourceEnd);
        		}
        		TypeBinding [] parameters = Binding.NO_PARAMETERS;
        		if (parametersLength > 1) {
        			parameters = new TypeBinding[parametersLength - 1];
        			System.arraycopy(descriptorParameters, 1, parameters, 0, parametersLength - 1);
        		}
        		this.depth = 0;
        		this.freeParameters = parameters;
        		anotherMethod = scope.getMethod(typeToSearch, this.selector, parameters, this);
        		anotherMethodDepth = this.depth;
        		this.depth = 0;
        	}
        }

        if (someMethod != null && someMethod.isValidBinding() && someMethod.isStatic() && anotherMethod != null && anotherMethod.isValidBinding() && !anotherMethod.isStatic()) {
        	scope.problemReporter().methodReferenceSwingsBothWays(this, anotherMethod, someMethod);
        	return this.resolvedType = null;
        }

        if (someMethod != null && someMethod.isValidBinding() && (anotherMethod == null || !anotherMethod.isValidBinding() || anotherMethod.isStatic())) {
        	this.binding = someMethod;
        	this.bits &= ~ASTNode.DepthMASK;
        	if (someMethodDepth > 0) {
        		this.bits |= (someMethodDepth & 0xFF) << ASTNode.DepthSHIFT;
        	}
        	if (!this.haveReceiver) {
        		if (!someMethod.isStatic() && !someMethod.isConstructor()) {
        			scope.problemReporter().methodMustBeAccessedWithInstance(this, someMethod);
        			return this.resolvedType = null;
        		}
        	}
        } else if (anotherMethod != null && anotherMethod.isValidBinding() && (someMethod == null || !someMethod.isValidBinding() || !someMethod.isStatic())) {
        	this.binding = anotherMethod;
        	this.receiverPrecedesParameters = true; // 0 is receiver, real parameters start at 1
        	this.bits &= ~ASTNode.DepthMASK;
        	if (anotherMethodDepth > 0) {
        		this.bits |= (anotherMethodDepth & 0xFF) << ASTNode.DepthSHIFT;
        	}
        	if (anotherMethod.isStatic()) {
        		scope.problemReporter().methodMustBeAccessedStatically(this, anotherMethod);
        		return this.resolvedType = null;
        	}
        } else {
        	this.binding = null;
        	this.bits &= ~ASTNode.DepthMASK;
        }

        if (this.binding == null) {
        	char [] visibleName = isConstructorReference() ? this.receiverType.sourceName() : this.selector;
        	scope.problemReporter().danglingReference(this, this.receiverType, visibleName, descriptorParameters);
			return this.resolvedType = null;
        }

        // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=382350#c2, I.super::abstractMethod will be handled there.

        if (this.binding.isAbstract() && this.lhs.isSuper())
        	scope.problemReporter().cannotDireclyInvokeAbstractMethod(this, this.binding);

        if (this.binding.isStatic()) {
        	if (TypeBinding.notEquals(this.binding.declaringClass, this.receiverType))
        		scope.problemReporter().indirectAccessToStaticMethod(this, this.binding);
        } else {
        	AbstractMethodDeclaration srcMethod = this.binding.sourceMethod();
        	if (srcMethod != null && srcMethod.isMethod())
        		srcMethod.bits &= ~ASTNode.CanBeStatic;
        }

    	if (isMethodUseDeprecated(this.binding, scope, true, this))
    		scope.problemReporter().deprecatedMethod(this.binding, this);

    	if (this.typeArguments != null && this.binding.original().typeVariables == Binding.NO_TYPE_VARIABLES)
    		scope.problemReporter().unnecessaryTypeArgumentsForMethodInvocation(this.binding, this.resolvedTypeArguments, this.typeArguments);

    	if ((this.binding.tagBits & TagBits.HasMissingType) != 0)
    		scope.problemReporter().missingTypeInMethod(this, this.binding);


        // OK, we have a compile time declaration, see if it passes muster.
        TypeBinding [] methodExceptions = this.binding.thrownExceptions;
        TypeBinding [] kosherExceptions = this.descriptor.thrownExceptions;
        next: for (int i = 0, iMax = methodExceptions.length; i < iMax; i++) {
        	if (methodExceptions[i].isUncheckedException(false)) {
        		continue next;
    		}
        	for (int j = 0, jMax = kosherExceptions.length; j < jMax; j++) {
        		if (methodExceptions[i].isCompatibleWith(kosherExceptions[j], scope))
        			continue next;
        	}
        	scope.problemReporter().unhandledException(methodExceptions[i], this);
        }
        checkNullAnnotations(scope);
        this.freeParameters = null; // not used after method lookup

    	if (checkInvocationArguments(scope, null, this.receiverType, this.binding, null, descriptorParameters, false, this))
    		this.bits |= ASTNode.Unchecked;

    	if (this.descriptor.returnType.id != TypeIds.T_void) {
    		TypeBinding returnType = null;
    		if (this.binding.isConstructor()) {
    			returnType = this.receiverType;
    		} else {
    			if ((this.bits & ASTNode.Unchecked) != 0 && this.resolvedTypeArguments == null) {
    				returnType = this.binding.returnType;
    				if (returnType != null) {
    					returnType = scope.environment().convertToRawType(returnType.erasure(), true);
    				}
    			} else {
    				returnType = this.binding.returnType;
    				if (returnType != null) {
    					returnType = returnType.capture(scope, this.sourceStart, this.sourceEnd);
    				}
    			}
    		}
    		if (this.descriptor.returnType.isProperType(true) // otherwise we cannot yet check compatibility
    				&& !returnType.isCompatibleWith(this.descriptor.returnType, scope)
    				&& !isBoxingCompatible(returnType, this.descriptor.returnType, this, scope))
    		{
    			scope.problemReporter().incompatibleReturnType(this, this.binding, this.descriptor.returnType);
    			this.binding = null;
    			this.resolvedType = null;
    		}
    	}

    	return this.resolvedType; // Phew !
	}

	protected void checkNullAnnotations(BlockScope scope) {
		CompilerOptions compilerOptions = scope.compilerOptions();
		if (compilerOptions.isAnnotationBasedNullAnalysisEnabled) {
        	if (this.expectedType == null || !NullAnnotationMatching.hasContradictions(this.expectedType)) { // otherwise assume it has been reported and we can do nothing here
        		ImplicitNullAnnotationVerifier.ensureNullnessIsKnown(this.binding, scope);
	        	// TODO: simplify by using this.freeParameters?
	        	int len;
	        	int expectedlen = this.binding.parameters.length;
	        	int providedLen = this.descriptor.parameters.length;
	        	if (this.receiverPrecedesParameters) {
	        		providedLen--; // one parameter is 'consumed' as the receiver

	        		TypeBinding descriptorParameter = this.descriptor.parameters[0];
	    			if((descriptorParameter.tagBits & TagBits.AnnotationNullable) != 0) { // Note: normal dereferencing of 'unchecked' values is not reported, either
		    			final TypeBinding receiver = scope.environment().createAnnotatedType(this.binding.declaringClass,
								new AnnotationBinding[] { scope.environment().getNonNullAnnotation() });
    					scope.problemReporter().referenceExpressionArgumentNullityMismatch(this, receiver, descriptorParameter, this.descriptor, -1, NullAnnotationMatching.NULL_ANNOTATIONS_MISMATCH);
	    			}
	        	}
	        	boolean isVarArgs = false;
	        	if (this.binding.isVarargs()) {
	        		isVarArgs = (providedLen == expectedlen)
						? !this.descriptor.parameters[expectedlen-1].isCompatibleWith(this.binding.parameters[expectedlen-1])
						: true;
	        		len = providedLen; // binding parameters will be padded from InferenceContext18.getParameter()
	        	} else {
	        		len = Math.min(expectedlen, providedLen);
	        	}
	    		for (int i = 0; i < len; i++) {
	    			TypeBinding descriptorParameter = this.descriptor.parameters[i + (this.receiverPrecedesParameters ? 1 : 0)];
	    			TypeBinding bindingParameter = InferenceContext18.getParameter(this.binding.parameters, i, isVarArgs);
					TypeBinding bindingParameterToCheck;
					if (bindingParameter.isPrimitiveType() && !descriptorParameter.isPrimitiveType()) {
						// replace primitive types by boxed equivalent for checking, e.g. int -> @NonNull Integer
						bindingParameterToCheck = scope.environment().createAnnotatedType(scope.boxing(bindingParameter),
								new AnnotationBinding[] { scope.environment().getNonNullAnnotation() });
					} else {
						bindingParameterToCheck = bindingParameter;
					}
	    			NullAnnotationMatching annotationStatus = NullAnnotationMatching.analyse(bindingParameterToCheck, descriptorParameter, FlowInfo.UNKNOWN);
	    			if (annotationStatus.isAnyMismatch()) {
	    				// immediate reporting:
	    				scope.problemReporter().referenceExpressionArgumentNullityMismatch(this, bindingParameter, descriptorParameter, this.descriptor, i, annotationStatus);
	    			}
	    		}
	    		TypeBinding returnType = this.binding.returnType;
	    		if(!returnType.isPrimitiveType()) {
		    		if (this.binding.isConstructor()) {
		    			returnType = scope.environment().createAnnotatedType(this.receiverType, new AnnotationBinding[]{ scope.environment().getNonNullAnnotation() });
		    		}
		    		NullAnnotationMatching annotationStatus = NullAnnotationMatching.analyse(this.descriptor.returnType, returnType, FlowInfo.UNKNOWN);
		        	if (annotationStatus.isAnyMismatch()) {
	        			scope.problemReporter().illegalReturnRedefinition(this, this.descriptor, annotationStatus.isUnchecked(), returnType);
		        	}
	    		}
        	}
        }
	}

	private TypeBinding[] descriptorParametersAsArgumentExpressions() {

		if (this.descriptor == null || this.descriptor.parameters == null || this.descriptor.parameters.length == 0)
			return Binding.NO_PARAMETERS;

		/* 15.13.1, " ... method reference is treated as if it were an invocation with argument expressions of types P1, ..., Pn;"
		   This implies/requires wildcard capture. This creates interesting complications, we can't just take the descriptor parameters
		   and apply captures - where a single wildcard type got "fanned out" and propagated into multiple locations through type variable
		   substitutions, we will end up creating distinct captures defeating the very idea of capture. We need to first capture and then
		   fan out. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=432759.
		*/
		if (this.expectedType.isParameterizedType()) {
			ParameterizedTypeBinding type = (ParameterizedTypeBinding) this.expectedType;
			MethodBinding method = type.getSingleAbstractMethod(this.enclosingScope, true, this.sourceStart, this.sourceEnd);
			return method.parameters;
		}
		return this.descriptor.parameters;
	}

	private boolean contextHasSyntaxError() {
		ReferenceContext referenceContext = this.enclosingScope.referenceContext();
		if (referenceContext instanceof AbstractMethodDeclaration) {
			if ((((AbstractMethodDeclaration) referenceContext).bits & ASTNode.HasSyntaxErrors) != 0)
				return true;
		}
		return false;
	}

	// Cache resolved copies against various target types, so repeat overload resolution and possibly type inference could be avoided.
	private ReferenceExpression cachedResolvedCopy(TypeBinding targetType) {

		ReferenceExpression copy = this.copiesPerTargetType != null ? this.copiesPerTargetType.get(targetType) : null;
		if (copy != null)
			return copy;

		if (contextHasSyntaxError())
			return null;

		IErrorHandlingPolicy oldPolicy = this.enclosingScope.problemReporter().switchErrorHandlingPolicy(silentErrorHandlingPolicy);
		try {
			copy = copy();
			if (copy == null) { // should never happen even for code assist.
				return null;
			}
			copy.setExpressionContext(this.expressionContext);
			copy.setExpectedType(targetType);
			copy.resolveType(this.enclosingScope);

			if (this.copiesPerTargetType == null)
				this.copiesPerTargetType = new HashMap<TypeBinding, ReferenceExpression>();
			this.copiesPerTargetType.put(targetType, copy);

			return copy;
		} finally {
			this.enclosingScope.problemReporter().switchErrorHandlingPolicy(oldPolicy);
		}
	}

	public void registerInferenceContext(ParameterizedGenericMethodBinding method, InferenceContext18 context) {
		if (this.inferenceContexts == null)
			this.inferenceContexts = new HashMap<ParameterizedGenericMethodBinding, InferenceContext18>();
		this.inferenceContexts.put(method, context);
	}

	public InferenceContext18 getInferenceContext(ParameterizedMethodBinding method) {
		if (this.inferenceContexts == null)
			return null;
		return this.inferenceContexts.get(method);
	}

	@Override
	public ReferenceExpression resolveExpressionExpecting(TypeBinding targetType, Scope scope, InferenceContext18 inferenceContext) {
		if (this.exactMethodBinding != null) { // We may see inference variables in target type.
			MethodBinding functionType = targetType.getSingleAbstractMethod(scope, true);
			if (functionType == null || functionType.problemId() == ProblemReasons.NoSuchSingleAbstractMethod)
				return null;
			int n = functionType.parameters.length;
			int k = this.exactMethodBinding.parameters.length;

			if (!this.haveReceiver && this.isMethodReference() && !this.exactMethodBinding.isStatic()) {
				k++;
			}
			return (n == k) ? this : null;
		}
		// descriptors parameters should be free of inference variables.
		ReferenceExpression copy = cachedResolvedCopy(targetType);
		return copy != null && copy.resolvedType != null && copy.resolvedType.isValidBinding() && copy.binding != null && copy.binding.isValidBinding() ? copy : null;
	}

	public boolean isConstructorReference() {
		return CharOperation.equals(this.selector,  ConstantPool.Init);
	}

	@Override
	public boolean isExactMethodReference() {
		return this.exactMethodBinding != null;
	}

	public MethodBinding getExactMethod() {
		return this.exactMethodBinding;
	}

	public boolean isMethodReference() {
		return !CharOperation.equals(this.selector,  ConstantPool.Init);
	}

	@Override
	public boolean isPertinentToApplicability(TypeBinding targetType, MethodBinding method) {
		if (!this.isExactMethodReference()) {
			return false;
		}
		return super.isPertinentToApplicability(targetType, method);
	}

	@Override
	public TypeBinding[] genericTypeArguments() {
		return this.resolvedTypeArguments;
	}

	@Override
	public InferenceContext18 freshInferenceContext(Scope scope) {
		if (this.expressionContext != ExpressionContext.VANILLA_CONTEXT) {
			Expression[] arguments = createPseudoExpressions(this.freeParameters);
			return new InferenceContext18(scope, arguments, this, null);
		}
		return null; // shouldn't happen, actually
	}

	@Override
	public boolean isSuperAccess() {
		return this.lhs.isSuper();
	}

	@Override
	public boolean isTypeAccess() {
		return !this.haveReceiver;
	}

	@Override
	public void setActualReceiverType(ReferenceBinding receiverType) {
		return;
	}

	@Override
	public void setDepth(int depth) {
		this.depth = depth;
	}

	@Override
	public void setFieldIndex(int depth) {
		return;
	}

	@Override
	public StringBuffer printExpression(int tab, StringBuffer output) {

		this.lhs.print(0, output);
		output.append("::"); //$NON-NLS-1$
		if (this.typeArguments != null) {
			output.append('<');
			int max = this.typeArguments.length - 1;
			for (int j = 0; j < max; j++) {
				this.typeArguments[j].print(0, output);
				output.append(", ");//$NON-NLS-1$
			}
			this.typeArguments[max].print(0, output);
			output.append('>');
		}
		if (isConstructorReference())
			output.append("new"); //$NON-NLS-1$
		else
			output.append(this.selector);

		return output;
	}

	@Override
	public void traverse(ASTVisitor visitor, BlockScope blockScope) {

		if (visitor.visit(this, blockScope)) {

			this.lhs.traverse(visitor, blockScope);

			int length = this.typeArguments == null ? 0 : this.typeArguments.length;
			for (int i = 0; i < length; i++) {
				this.typeArguments[i].traverse(visitor, blockScope);
			}
		}
		visitor.endVisit(this, blockScope);
	}

	public Expression[] createPseudoExpressions(TypeBinding[] p) {
		// from 15.13.1:
		// ... the reference is treated as if it were an invocation with argument expressions of types P1..Pn
		// ... the reference is treated as if it were an invocation with argument expressions of types P2..Pn
		// (the different sets of types are passed from our resolveType to scope.getMethod(..), see someMethod, anotherMethod)
		Expression[] expressions = new Expression[p.length];
		long pos = (((long)this.sourceStart)<<32)+this.sourceEnd;
		for (int i = 0; i < p.length; i++) {
			expressions[i] = new SingleNameReference(("fakeArg"+i).toCharArray(), pos); //$NON-NLS-1$
			expressions[i].resolvedType = p[i];
		}
		return expressions;
	}

	@Override
	public boolean isPotentiallyCompatibleWith(TypeBinding targetType, Scope scope) {

        final boolean isConstructorRef = isConstructorReference();
		if (isConstructorRef) {
			if (this.receiverType == null)
				return false;
			if (this.receiverType.isArrayType()) {
				final TypeBinding leafComponentType = this.receiverType.leafComponentType();
				if (!leafComponentType.isReifiable()) {
					return false;
				}
			}
		}

		// We get here only when the reference expression is NOT pertinent to applicability.
		if (!super.isPertinentToApplicability(targetType, null))
			return true;
		final MethodBinding sam = targetType.getSingleAbstractMethod(this.enclosingScope, true);
		if (sam == null || !sam.isValidBinding())
			return false;
		if (this.typeArgumentsHaveErrors || this.receiverType == null || !this.receiverType.isValidBinding())
			return false;

		int parametersLength = sam.parameters.length;
		TypeBinding[] descriptorParameters = new TypeBinding[parametersLength];
		for (int i = 0; i < parametersLength; i++) {
			descriptorParameters[i] = new ReferenceBinding() {
				{
					this.compoundName = CharOperation.NO_CHAR_CHAR;
				}
				@Override
				public boolean isCompatibleWith(TypeBinding otherType, Scope captureScope) {
					return true;
				}
				@Override
				public TypeBinding findSuperTypeOriginatingFrom(TypeBinding otherType) {
					return otherType;
				}
				@Override
				public String toString() {
					return "(wildcard)"; //$NON-NLS-1$
				}
			};
		}

		// 15.13.1
        this.freeParameters = descriptorParameters;
        this.checkingPotentialCompatibility = true;
        try {
			MethodBinding compileTimeDeclaration = getCompileTimeDeclaration(scope, isConstructorRef, descriptorParameters);

        	if (compileTimeDeclaration != null && compileTimeDeclaration.isValidBinding()) // we have the mSMB.
        		this.potentialMethods = new MethodBinding [] { compileTimeDeclaration };
        	else {
        		/* We EITHER have potential methods that are input to Scope.mSMb already captured in this.potentialMethods
        	       OR there is no potentially compatible compile time declaration ...
        		 */
        	}

        	/* 15.12.2.1: A method reference expression (§15.13) is potentially compatible with a functional interface type if, where the type's function type arity is n,
		       there exists at least one potentially applicable method for the method reference expression with arity n (§15.13.1), and one of the following is true:
                   – The method reference expression has the form ReferenceType ::[TypeArguments] Identifier and at least one potentially applicable method is
                        i) static and supports arity n, or ii) not static and supports arity n-1.
                   – The method reference expression has some other form and at least one potentially applicable method is not static.
        	*/

        	for (int i = 0, length = this.potentialMethods.length; i < length; i++) {
        		if (this.potentialMethods[i].isStatic() || this.potentialMethods[i].isConstructor()) {
        			if (!this.haveReceiver) // form ReferenceType ::[TypeArguments] Identifier
        				return true;
        		} else {
        			if (this.haveReceiver) // some other form.
        				return true;
        		}
        	}

        	if (this.haveReceiver || parametersLength == 0)
        		return false;

        	System.arraycopy(descriptorParameters, 1, descriptorParameters = new TypeBinding[parametersLength - 1], 0, parametersLength - 1);
        	this.freeParameters = descriptorParameters;
        	this.potentialMethods = Binding.NO_METHODS;
        	compileTimeDeclaration = getCompileTimeDeclaration(scope, false, descriptorParameters);

        	if (compileTimeDeclaration != null && compileTimeDeclaration.isValidBinding()) // we have the mSMB.
        		this.potentialMethods = new MethodBinding [] { compileTimeDeclaration };
        	else {
        		/* We EITHER have potential methods that are input to Scope.mSMb already captured in this.potentialMethods
              	   OR there is no potentially compatible compile time declaration ...
        		*/
        	}
        	for (int i = 0, length = this.potentialMethods.length; i < length; i++) {
			if (!this.potentialMethods[i].isStatic() && !this.potentialMethods[i].isConstructor()) {
        			return true;
        		}
        	}
        } finally {
        	this.checkingPotentialCompatibility = false;
        	this.potentialMethods = Binding.NO_METHODS;
        	this.freeParameters = null; // not used after method lookup
        }
        return false;
	}

	MethodBinding getCompileTimeDeclaration(Scope scope, boolean isConstructorRef, TypeBinding[] parameters) {
		if (this.exactMethodBinding != null)
			return this.exactMethodBinding;
		else if (this.receiverType.isArrayType())
			return scope.findMethodForArray((ArrayBinding) this.receiverType, this.selector, Binding.NO_PARAMETERS, this);
		else if (isConstructorRef)
			return scope.getConstructor((ReferenceBinding) this.receiverType, parameters, this);
		else
			return scope.getMethod(this.receiverType, this.selector, parameters, this);
	}

	@Override
	public boolean isCompatibleWith(TypeBinding targetType, Scope scope) {
		ReferenceExpression copy = cachedResolvedCopy(targetType);
		if (copy == null) {
			return contextHasSyntaxError(); // in case of syntax error avoid secondary errors
		} else {
			return copy.resolvedType != null && copy.resolvedType.isValidBinding() && copy.binding != null && copy.binding.isValidBinding();
		}
	}

	@Override
	public boolean sIsMoreSpecific(TypeBinding s, TypeBinding t, Scope scope) {

		if (super.sIsMoreSpecific(s, t, scope))
			return true;

		if (this.exactMethodBinding == null || t.findSuperTypeOriginatingFrom(s) != null)
			return false;

		s = s.capture(this.enclosingScope, this.sourceStart, this.sourceEnd);
		MethodBinding sSam = s.getSingleAbstractMethod(this.enclosingScope, true);
		if (sSam == null || !sSam.isValidBinding())
			return false;
		TypeBinding r1 = sSam.returnType;

		MethodBinding tSam = t.getSingleAbstractMethod(this.enclosingScope, true);
		if (tSam == null || !tSam.isValidBinding())
			return false;
		TypeBinding r2 = tSam.returnType;

		TypeBinding[] sParams = sSam.parameters;
		TypeBinding[] tParams = tSam.parameters;
		// Both must have the same number of parameters if we got this far
		for (int i = 0; i < sParams.length; i++) {
			if (TypeBinding.notEquals(sParams[i], tParams[i]))
				return false;
		}
		if (r2.id == TypeIds.T_void)
			return true;

		if (r1.id == TypeIds.T_void)
			return false;

		// r1 <: r2
		if (r1.isCompatibleWith(r2, scope))
			return true;

		return r1.isBaseType() != r2.isBaseType() && r1.isBaseType() == this.exactMethodBinding.returnType.isBaseType();
	}

	@Override
	public org.aspectj.org.eclipse.jdt.internal.compiler.lookup.MethodBinding getMethodBinding() {
		if (this.actualMethodBinding == null)  // array new/clone, no real binding.
			this.actualMethodBinding = this.binding;
		return this.actualMethodBinding;
	}

	public boolean isArrayConstructorReference() {
		return isConstructorReference() && this.lhs.resolvedType != null && this.lhs.resolvedType.isArrayType();
	}
}
