/*******************************************************************************
* Copyright (c) 2000, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
/**
* Binding denoting a generic method after type parameter substitutions got performed. On
* parameterized type bindings, all methods got substituted, regardless whether their signature did
* involve generics or not, so as to get the proper declaringClass for these methods.
*/
public class ParameterizedGenericMethodBinding extends ParameterizedMethodBinding implements Substitution {
public TypeBinding[] typeArguments;
private LookupEnvironment environment;
public boolean inferredReturnType;
public boolean wasInferred; // only set to true for instances resulting from method invocation inferrence
public boolean isRaw; // set to true for method behaving as raw for substitution purpose
private MethodBinding tiebreakMethod;
/**
* Perform inference of generic method type parameters and/or expected type
*/
public static MethodBinding computeCompatibleMethod(MethodBinding originalMethod, TypeBinding[] arguments, Scope scope, InvocationSite invocationSite) {
ParameterizedGenericMethodBinding methodSubstitute;
TypeVariableBinding[] typeVariables= originalMethod.typeVariables;
TypeBinding[] substitutes= invocationSite.genericTypeArguments();
TypeBinding[] uncheckedArguments= null;
computeSubstitutes: {
if (substitutes != null) {
// explicit type arguments got supplied
if (substitutes.length != typeVariables.length) {
// incompatible due to wrong arity
return new ProblemMethodBinding(originalMethod, originalMethod.selector, substitutes, ProblemReasons.TypeParameterArityMismatch);
}
methodSubstitute= scope.environment().createParameterizedGenericMethod(originalMethod, substitutes);
break computeSubstitutes;
}
// perform type argument inference (15.12.2.7)
// initializes the map of substitutes (var --> type[][]{ equal, extends, super}
TypeBinding[] parameters= originalMethod.parameters;
InferenceContext inferenceContext= new InferenceContext(originalMethod);
methodSubstitute= inferFromArgumentTypes(scope, originalMethod, arguments, parameters, inferenceContext);
if (methodSubstitute == null)
return null;
// substitutes may hold null to denote unresolved vars, but null arguments got replaced with respective original variable in param method
// 15.12.2.8 - inferring unresolved type arguments
if (inferenceContext.hasUnresolvedTypeArgument()) {
if (inferenceContext.isUnchecked) { // only remember unchecked status post 15.12.2.7
int length= inferenceContext.substitutes.length;
System.arraycopy(inferenceContext.substitutes, 0, uncheckedArguments= new TypeBinding[length], 0, length);
}
if (methodSubstitute.returnType != TypeBinding.VOID) {
TypeBinding expectedType= null;
// if message invocation has expected type
if (invocationSite instanceof MessageSend) {
MessageSend message= (MessageSend)invocationSite;
expectedType= message.expectedType;
}
if (expectedType != null) {
// record it was explicit from context, as opposed to assumed by default (see below)
inferenceContext.hasExplicitExpectedType= true;
} else {
expectedType= scope.getJavaLangObject(); // assume Object by default
}
inferenceContext.expectedType= expectedType;
}
methodSubstitute= methodSubstitute.inferFromExpectedType(scope, inferenceContext);
if (methodSubstitute == null)
return null;
}
}
// bounds check
for (int i= 0, length= typeVariables.length; i < length; i++) {
TypeVariableBinding typeVariable= typeVariables[i];
TypeBinding substitute= methodSubstitute.typeArguments[i];
if (uncheckedArguments != null && uncheckedArguments[i] == null)
continue; // only bound check if inferred through 15.12.2.6
switch (typeVariable.boundCheck(methodSubstitute, substitute)) {
case TypeConstants.MISMATCH:
// incompatible due to bound check
int argLength= arguments.length;
TypeBinding[] augmentedArguments= new TypeBinding[argLength + 2]; // append offending substitute and typeVariable
System.arraycopy(arguments, 0, augmentedArguments, 0, argLength);
augmentedArguments[argLength]= substitute;
augmentedArguments[argLength + 1]= typeVariable;
return new ProblemMethodBinding(methodSubstitute, originalMethod.selector, augmentedArguments, ProblemReasons.ParameterBoundMismatch);
case TypeConstants.UNCHECKED:
// tolerate unchecked bounds
methodSubstitute.tagBits|= TagBits.HasUncheckedTypeArgumentForBoundCheck;
break;
}
}
// check presence of unchecked argument conversion a posteriori (15.12.2.6)
return methodSubstitute;
}
/**
* Collect argument type mapping, handling varargs
*/
private static ParameterizedGenericMethodBinding inferFromArgumentTypes(Scope scope, MethodBinding originalMethod, TypeBinding[] arguments, TypeBinding[] parameters,
InferenceContext inferenceContext) {
if (originalMethod.isVarargs()) {
int paramLength= parameters.length;
int minArgLength= paramLength - 1;
int argLength= arguments.length;
// process mandatory arguments
for (int i= 0; i < minArgLength; i++) {
parameters[i].collectSubstitutes(scope, arguments[i], inferenceContext, TypeConstants.CONSTRAINT_EXTENDS);
if (inferenceContext.status == InferenceContext.FAILED)
return null; // impossible substitution
}
// process optional arguments
if (minArgLength < argLength) {
TypeBinding varargType= parameters[minArgLength]; // last arg type - as is ?
TypeBinding lastArgument= arguments[minArgLength];
checkVarargDimension: {
if (paramLength == argLength) {
if (lastArgument == TypeBinding.NULL)
break checkVarargDimension;
switch (lastArgument.dimensions()) {
case 0:
break; // will remove one dim
case 1:
if (!lastArgument.leafComponentType().isBaseType())
break checkVarargDimension;
break; // will remove one dim
default:
break checkVarargDimension;
}
}
// eliminate one array dimension
varargType= ((ArrayBinding)varargType).elementsType();
}
for (int i= minArgLength; i < argLength; i++) {
varargType.collectSubstitutes(scope, arguments[i], inferenceContext, TypeConstants.CONSTRAINT_EXTENDS);
if (inferenceContext.status == InferenceContext.FAILED)
return null; // impossible substitution
}
}
} else {
int paramLength= parameters.length;
for (int i= 0; i < paramLength; i++) {
parameters[i].collectSubstitutes(scope, arguments[i], inferenceContext, TypeConstants.CONSTRAINT_EXTENDS);
if (inferenceContext.status == InferenceContext.FAILED)
return null; // impossible substitution
}
}
TypeVariableBinding[] originalVariables= originalMethod.typeVariables;
if (!resolveSubstituteConstraints(scope, originalVariables, inferenceContext, false/*ignore Ti<:Uk*/))
return null; // impossible substitution
// apply inferred variable substitutions - replacing unresolved variable with original ones in param method
TypeBinding[] inferredSustitutes= inferenceContext.substitutes;
TypeBinding[] actualSubstitutes= inferredSustitutes;
for (int i= 0, varLength= originalVariables.length; i < varLength; i++) {
if (inferredSustitutes[i] == null) {
if (actualSubstitutes == inferredSustitutes) {
System.arraycopy(inferredSustitutes, 0, actualSubstitutes= new TypeBinding[varLength], 0, i); // clone to replace null with original variable in param method
}
actualSubstitutes[i]= originalVariables[i];
} else if (actualSubstitutes != inferredSustitutes) {
actualSubstitutes[i]= inferredSustitutes[i];
}
}
ParameterizedGenericMethodBinding paramMethod= scope.environment().createParameterizedGenericMethod(originalMethod, actualSubstitutes);
return paramMethod;
}
private static boolean resolveSubstituteConstraints(Scope scope, TypeVariableBinding[] typeVariables, InferenceContext inferenceContext, boolean considerEXTENDSConstraints) {
TypeBinding[] substitutes= inferenceContext.substitutes;
int varLength= typeVariables.length;
// check Tj=U constraints
nextTypeParameter: for (int i= 0; i < varLength; i++) {
TypeVariableBinding current= typeVariables[i];
TypeBinding substitute= substitutes[i];
if (substitute != null)
continue nextTypeParameter; // already inferred previously
TypeBinding[] equalSubstitutes= inferenceContext.getSubstitutes(current, TypeConstants.CONSTRAINT_EQUAL);
if (equalSubstitutes != null) {
nextConstraint: for (int j= 0, equalLength= equalSubstitutes.length; j < equalLength; j++) {
TypeBinding equalSubstitute= equalSubstitutes[j];
if (equalSubstitute == null)
continue nextConstraint;
if (equalSubstitute == current) {
// try to find a better different match if any in subsequent equal candidates
for (int k= j + 1; k < equalLength; k++) {
equalSubstitute= equalSubstitutes[k];
if (equalSubstitute != current && equalSubstitute != null) {
substitutes[i]= equalSubstitute;
continue nextTypeParameter;
}
}
substitutes[i]= current;
continue nextTypeParameter;
}
// if (equalSubstitute.isTypeVariable()) {
// TypeVariableBinding variable = (TypeVariableBinding) equalSubstitute;
// // substituted by a variable of the same method, ignore
// if (variable.rank < varLength && typeVariables[variable.rank] == variable) {
// // TODO (philippe) rewrite all other constraints to use current instead.
// continue nextConstraint;
// }
// }
substitutes[i]= equalSubstitute;
continue nextTypeParameter; // pick first match, applicability check will rule out invalid scenario where others were present
}
}
}
if (inferenceContext.hasUnresolvedTypeArgument()) {
// check Tj>:U constraints
nextTypeParameter: for (int i= 0; i < varLength; i++) {
TypeVariableBinding current= typeVariables[i];
TypeBinding substitute= substitutes[i];
if (substitute != null)
continue nextTypeParameter; // already inferred previously
TypeBinding[] bounds= inferenceContext.getSubstitutes(current, TypeConstants.CONSTRAINT_SUPER);
if (bounds == null)
continue nextTypeParameter;
TypeBinding mostSpecificSubstitute= scope.lowerUpperBound(bounds);
if (mostSpecificSubstitute == null) {
return false; // incompatible
}
if (mostSpecificSubstitute != TypeBinding.VOID) {
substitutes[i]= mostSpecificSubstitute;
}
}
}
if (considerEXTENDSConstraints && inferenceContext.hasUnresolvedTypeArgument()) {
// check Tj<:U constraints
nextTypeParameter: for (int i= 0; i < varLength; i++) {
TypeVariableBinding current= typeVariables[i];
TypeBinding substitute= substitutes[i];
if (substitute != null)
continue nextTypeParameter; // already inferred previously
TypeBinding[] bounds= inferenceContext.getSubstitutes(current, TypeConstants.CONSTRAINT_EXTENDS);
if (bounds == null)
continue nextTypeParameter;
TypeBinding[] glb= Scope.greaterLowerBound(bounds);
TypeBinding mostSpecificSubstitute= null;
if (glb != null)
mostSpecificSubstitute= glb[0]; // TODO (philippe) need to improve
//TypeBinding mostSpecificSubstitute = scope.greaterLowerBound(bounds);
if (mostSpecificSubstitute != null) {
substitutes[i]= mostSpecificSubstitute;
}
}
}
return true;
}
/**
* Create raw generic method for raw type (double substitution from type vars with raw type
* arguments, and erasure of method variables) Only invoked for non-static generic methods of
* raw type
*/
public ParameterizedGenericMethodBinding(MethodBinding originalMethod, RawTypeBinding rawType, LookupEnvironment environment) {
TypeVariableBinding[] originalVariables= originalMethod.typeVariables;
int length= originalVariables.length;
TypeBinding[] rawArguments= new TypeBinding[length];
for (int i= 0; i < length; i++) {
rawArguments[i]= environment.convertToRawType(originalVariables[i].erasure(), false /*do not force conversion of enclosing types*/);
}
this.isRaw= true;
this.tagBits= originalMethod.tagBits;
this.environment= environment;
this.modifiers= originalMethod.modifiers;
this.selector= originalMethod.selector;
this.declaringClass= rawType == null ? originalMethod.declaringClass : rawType;
this.typeVariables= Binding.NO_TYPE_VARIABLES;
this.typeArguments= rawArguments;
this.originalMethod= originalMethod;
boolean ignoreRawTypeSubstitution= rawType == null || originalMethod.isStatic();
this.parameters= Scope.substitute(this, ignoreRawTypeSubstitution
? originalMethod.parameters // no substitution if original was static
: Scope.substitute(rawType, originalMethod.parameters));
this.thrownExceptions= Scope.substitute(this, ignoreRawTypeSubstitution
? originalMethod.thrownExceptions // no substitution if original was static
: Scope.substitute(rawType, originalMethod.thrownExceptions));
// error case where exception type variable would have been substituted by a non-reference type (207573)
if (this.thrownExceptions == null)
this.thrownExceptions= Binding.NO_EXCEPTIONS;
this.returnType= Scope.substitute(this, ignoreRawTypeSubstitution
? originalMethod.returnType // no substitution if original was static
: Scope.substitute(rawType, originalMethod.returnType));
this.wasInferred= false; // not resulting from method invocation inferrence
}
/**
* Create method of parameterized type, substituting original parameters with type arguments.
*/
public ParameterizedGenericMethodBinding(MethodBinding originalMethod, TypeBinding[] typeArguments, LookupEnvironment environment) {
this.environment= environment;
this.modifiers= originalMethod.modifiers;
this.selector= originalMethod.selector;
this.declaringClass= originalMethod.declaringClass;
this.typeVariables= Binding.NO_TYPE_VARIABLES;
this.typeArguments= typeArguments;
this.isRaw= false;
this.tagBits= originalMethod.tagBits;
this.originalMethod= originalMethod;
this.parameters= Scope.substitute(this, originalMethod.parameters);
// error case where exception type variable would have been substituted by a non-reference type (207573)
this.returnType= Scope.substitute(this, originalMethod.returnType);
this.thrownExceptions= Scope.substitute(this, originalMethod.thrownExceptions);
if (this.thrownExceptions == null)
this.thrownExceptions= Binding.NO_EXCEPTIONS;
checkMissingType: {
if ((this.tagBits & TagBits.HasMissingType) != 0)
break checkMissingType;
if ((this.returnType.tagBits & TagBits.HasMissingType) != 0) {
this.tagBits|= TagBits.HasMissingType;
break checkMissingType;
}
for (int i= 0, max= this.parameters.length; i < max; i++) {
if ((this.parameters[i].tagBits & TagBits.HasMissingType) != 0) {
this.tagBits|= TagBits.HasMissingType;
break checkMissingType;
}
}
for (int i= 0, max= this.thrownExceptions.length; i < max; i++) {
if ((this.thrownExceptions[i].tagBits & TagBits.HasMissingType) != 0) {
this.tagBits|= TagBits.HasMissingType;
break checkMissingType;
}
}
}
this.wasInferred= true;// resulting from method invocation inferrence
}
/*
* parameterizedDeclaringUniqueKey dot selector originalMethodGenericSignature percent typeArguments
* p.X<U> { <T> void bar(T t, U u) { new X<String>().bar(this, "") } } --> Lp/X<Ljava/lang/String;>;.bar<T:Ljava/lang/Object;>(TT;Ljava/lang/String;)V%<Lp/X;>
*/
public char[] computeUniqueKey(boolean isLeaf) {
StringBuffer buffer= new StringBuffer();
buffer.append(this.originalMethod.computeUniqueKey(false/*not a leaf*/));
buffer.append('%');
buffer.append('<');
if (!this.isRaw) {
int length= this.typeArguments.length;
for (int i= 0; i < length; i++) {
TypeBinding typeArgument= this.typeArguments[i];
buffer.append(typeArgument.computeUniqueKey(false/*not a leaf*/));
}
}
buffer.append('>');
int resultLength= buffer.length();
char[] result= new char[resultLength];
buffer.getChars(0, resultLength, result, 0);
return result;
}
/**
* @see org.eclipse.jdt.internal.compiler.lookup.Substitution#environment()
*/
public LookupEnvironment environment() {
return this.environment;
}
/**
* Returns true if some parameters got substituted. NOTE: generic method invocation delegates to
* its declaring method (could be a parameterized one)
*/
public boolean hasSubstitutedParameters() {
// generic parameterized method can represent either an invocation or a raw generic method
if (this.wasInferred)
return this.originalMethod.hasSubstitutedParameters();
return super.hasSubstitutedParameters();
}
/**
* Returns true if the return type got substituted. NOTE: generic method invocation delegates to
* its declaring method (could be a parameterized one)
*/
public boolean hasSubstitutedReturnType() {
if (this.inferredReturnType)
return this.originalMethod.hasSubstitutedReturnType();
return super.hasSubstitutedReturnType();
}
/**
* Given some type expectation, and type variable bounds, perform some inference. Returns true
* if still had unresolved type variable at the end of the operation
*/
private ParameterizedGenericMethodBinding inferFromExpectedType(Scope scope, InferenceContext inferenceContext) {
TypeVariableBinding[] originalVariables= this.originalMethod.typeVariables; // immediate parent (could be a parameterized method)
int varLength= originalVariables.length;
// infer from expected return type
if (inferenceContext.expectedType != null) {
this.returnType.collectSubstitutes(scope, inferenceContext.expectedType, inferenceContext, TypeConstants.CONSTRAINT_SUPER);
if (inferenceContext.status == InferenceContext.FAILED)
return null; // impossible substitution
}
// infer from bounds of type parameters
for (int i= 0; i < varLength; i++) {
TypeVariableBinding originalVariable= originalVariables[i];
TypeBinding argument= this.typeArguments[i];
boolean argAlreadyInferred= argument != originalVariable;
if (originalVariable.firstBound == originalVariable.superclass) {
TypeBinding substitutedBound= Scope.substitute(this, originalVariable.superclass);
argument.collectSubstitutes(scope, substitutedBound, inferenceContext, TypeConstants.CONSTRAINT_SUPER);
if (inferenceContext.status == InferenceContext.FAILED)
return null; // impossible substitution
// JLS 15.12.2.8 claims reverse inference shouldn't occur, however it improves inference
// e.g. given: <E extends Object, S extends Collection<E>> S test1(S param)
// invocation: test1(new Vector<String>()) will infer: S=Vector<String> and with code below: E=String
if (argAlreadyInferred) {
substitutedBound.collectSubstitutes(scope, argument, inferenceContext, TypeConstants.CONSTRAINT_EXTENDS);
if (inferenceContext.status == InferenceContext.FAILED)
return null; // impossible substitution
}
}
for (int j= 0, max= originalVariable.superInterfaces.length; j < max; j++) {
TypeBinding substitutedBound= Scope.substitute(this, originalVariable.superInterfaces[j]);
argument.collectSubstitutes(scope, substitutedBound, inferenceContext, TypeConstants.CONSTRAINT_SUPER);
if (inferenceContext.status == InferenceContext.FAILED)
return null; // impossible substitution
// JLS 15.12.2.8 claims reverse inference shouldn't occur, however it improves inference
if (argAlreadyInferred) {
substitutedBound.collectSubstitutes(scope, argument, inferenceContext, TypeConstants.CONSTRAINT_EXTENDS);
if (inferenceContext.status == InferenceContext.FAILED)
return null; // impossible substitution
}
}
}
if (!resolveSubstituteConstraints(scope, originalVariables, inferenceContext, true/*consider Ti<:Uk*/))
return null; // incompatible
// this.typeArguments = substitutes; - no op since side effects got performed during #resolveSubstituteConstraints
for (int i= 0; i < varLength; i++) {
TypeBinding substitute= inferenceContext.substitutes[i];
if (substitute != null) {
this.typeArguments[i]= inferenceContext.substitutes[i];
} else {
// remaining unresolved variable are considered to be Object (or their bound actually)
this.typeArguments[i]= originalVariables[i].upperBound();
}
}
// may still need an extra substitution at the end (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=121369)
// to properly substitute a remaining unresolved variable which also appear in a formal bound
this.typeArguments= Scope.substitute(this, this.typeArguments);
// adjust method types to reflect latest inference
TypeBinding oldReturnType= this.returnType;
this.returnType= Scope.substitute(this, this.returnType);
this.inferredReturnType= inferenceContext.hasExplicitExpectedType && this.returnType != oldReturnType;
this.parameters= Scope.substitute(this, this.parameters);
this.thrownExceptions= Scope.substitute(this, this.thrownExceptions);
// error case where exception type variable would have been substituted by a non-reference type (207573)
if (this.thrownExceptions == null)
this.thrownExceptions= Binding.NO_EXCEPTIONS;
checkMissingType: {
if ((this.tagBits & TagBits.HasMissingType) != 0)
break checkMissingType;
if ((this.returnType.tagBits & TagBits.HasMissingType) != 0) {
this.tagBits|= TagBits.HasMissingType;
break checkMissingType;
}
for (int i= 0, max= this.parameters.length; i < max; i++) {
if ((this.parameters[i].tagBits & TagBits.HasMissingType) != 0) {
this.tagBits|= TagBits.HasMissingType;
break checkMissingType;
}
}
for (int i= 0, max= this.thrownExceptions.length; i < max; i++) {
if ((this.thrownExceptions[i].tagBits & TagBits.HasMissingType) != 0) {
this.tagBits|= TagBits.HasMissingType;
break checkMissingType;
}
}
}
return this;
}
/**
* @see org.eclipse.jdt.internal.compiler.lookup.Substitution#isRawSubstitution()
*/
public boolean isRawSubstitution() {
return this.isRaw;
}
/**
* @see org.eclipse.jdt.internal.compiler.lookup.Substitution#substitute(org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding)
*/
public TypeBinding substitute(TypeVariableBinding originalVariable) {
TypeVariableBinding[] variables= this.originalMethod.typeVariables;
int length= variables.length;
// check this variable can be substituted given parameterized type
if (originalVariable.rank < length && variables[originalVariable.rank] == originalVariable) {
return this.typeArguments[originalVariable.rank];
}
return originalVariable;
}
/**
* @see org.eclipse.jdt.internal.compiler.lookup.MethodBinding#tiebreakMethod()
*/
public MethodBinding tiebreakMethod() {
if (this.tiebreakMethod == null)
this.tiebreakMethod= this.originalMethod.asRawMethod(this.environment);
return this.tiebreakMethod;
}
}